#include "UOBJLoader.h"
#include "UIniFile/UIniFile.h"
#include "UTextureManager.h"
#include <string>
#include <fstream>
#include <iostream>
#include <boost/foreach.hpp>
using namespace std;

#define MODEL_BASE_PATH "Data/Models/" //FIXME: Ugly hax
static uint32 lpos; //FIXME: Zomg t3h fugly!!!

string TOBJLoader::GetNextBuf ( string& Line ) {
	while ( Line[lpos] == ' ' ) ++lpos;
	uint i = Line.find ( ' ',lpos );

	string s = Line.substr ( lpos,i-lpos );
	lpos = i;
	return s;
}

void TOBJLoader::ProcessLine ( string& Line ) {
	lpos = 0;
	string buffer = GetNextBuf ( Line );
	//string reserve = CropString(Line);

	if ( strcmp ( buffer.c_str(),"v" ) == 0 )
		// Current line describes a vector
		// Vector always has 3 floats
	{
		float x, y, z;
		x = atof ( GetNextBuf ( Line ).c_str() );
		y = atof ( GetNextBuf ( Line ).c_str() );
		z = atof ( GetNextBuf ( Line ).c_str() );
		TVector Vec ( x,y,z );
		Vectors.push_back ( Vec );
		return;// No need for something else :)
	}

	if ( strcmp ( buffer.c_str(), "g" ) == 0 ) {
		// We found a group
		// Shouldn't we do anything with it?
		cerr << "Found group '"+Line +"'" << endl;
		return;
	}


	if ( strcmp ( buffer.c_str(), "vt" ) == 0 ) {	//A texture vertex is specified here
		float x,y,z;
		x = atof ( GetNextBuf ( Line ).c_str() );
		y = atof ( GetNextBuf ( Line ).c_str() );
		z = atof ( GetNextBuf ( Line ).c_str() );
		TVector Vec ( x,y,z );
		TexVectors.push_back ( Vec );
		return;
	}

	if ( strcmp ( buffer.c_str(), "vn" ) == 0 ) {	// A normal vertex is specified here
		float x, y, z;
		x = atof ( GetNextBuf ( Line ).c_str() );
		y = atof ( GetNextBuf ( Line ).c_str() );
		z = atof ( GetNextBuf ( Line ).c_str() );
		TVector Vec ( x,y,z );
		Vec.Normalize();
		NVectors.push_back ( Vec );
		return;// No need for something else :)
	}

	if ( strcmp ( buffer.c_str(), "f" ) == 0 )
		/* Current line describes a face
		A face can have multiple vertices
		They can be accompanied by a texture vertex
		and/or a normal vertex. However, if a 'special'
		vertex is specified for one geometrical, it must
		be specified for all!

		*/
	{
		TFace AFace;
		while ( lpos < Line.size() ) {
			AFace = ProcessFaceArgument ( AFace, GetNextBuf ( Line ) );
		}
		TVector hulp1 = Vectors[AFace.GetGVector ( 0 ) ] - Vectors[AFace.GetGVector ( 1 ) ];
		TVector hulp2 = Vectors[AFace.GetGVector ( 2 ) ] - Vectors[AFace.GetGVector ( 1 ) ];
		TVector normvec = hulp2 * hulp1;
		normvec.Normalize();
		AFace.SetNormal ( normvec );
		Faces.push_back ( AFace );
	}
}

TFace TOBJLoader::ProcessFaceArgument ( TFace AFace, const string& arg ) {
	uint i = arg.find_first_of ( '/' );
	int readvar;// ReadVar is the read variable of the file
	if ( i == string::npos ) // no extra vertices
	{
		readvar = atoi ( arg.c_str() );
		readvar < 0 ? AFace.AddGVector ( Vectors.size() + readvar ) : AFace.AddGVector ( readvar - 1 );
		return AFace;
	}

	readvar = atoi ( string ( arg, 0, i ).c_str() );
	readvar < 0 ? AFace.AddGVector ( Vectors.size() + readvar ) : AFace.AddGVector ( readvar - 1 );
	uint j = arg.find_first_of ( '/',i+1 );
	if ( j == string::npos ) //only a texture vertex specified
	{
		readvar = atoi ( string ( arg, i + 1, arg.length() - i ).c_str() );
		readvar < 0 ? AFace.AddTVector ( TexVectors.size() + readvar ) : AFace.AddTVector ( readvar - 1 );

		AFace.hasTVecs = true;
		return AFace;
	}
	AFace.hasNormals = true;
	if ( j == i + 1 ) //Only a normal vertex specified
	{
		readvar = atoi ( string ( arg, j + 1, arg.size() - j ).c_str() );
		readvar < 0 ? AFace.AddNVector ( NVectors.size() + readvar ) : AFace.AddNVector ( readvar - 1 );
		return AFace;
	}
	AFace.hasTVecs = true; //both normal vertex and texture vertex specified

	readvar = atoi ( string ( arg, i + 1, arg.size() - i ).c_str() );
	readvar < 0 ? AFace.AddTVector ( TexVectors.size() + readvar ) : AFace.AddTVector ( readvar - 1 );

	readvar = atoi ( string ( arg, j + 1, arg.size() - j ).c_str() );
	readvar < 0 ? AFace.AddNVector ( NVectors.size() + readvar ) : AFace.AddNVector ( readvar - 1 );

	return AFace;
}

TModel* TOBJLoader::loadModelFromStream ( std::ifstream& stream ) {
	TModel* model = new TModel();
	Vectors.clear();
	NVectors.clear();
	TexVectors.clear();
	Faces.clear();

	TIniFile IniFile ( stream );
	string ModelPath = MODEL_BASE_PATH + IniFile.getValue ( "Model","File" );
	string TexturePath = IniFile.getValue ( "Texture","Textpath" );

	ifstream infile;
	infile.open ( ModelPath.c_str(), ios::in );
	if ( !infile.good() ) {
		cerr << getClassName() +"unable to open file " << ModelPath << " for reading\n";
		return model;
	}
	*infile.rdbuf()->pubsetbuf ( 0, 4096 );
	string CurrentLine;
	while ( !infile.eof() ) {
		std::getline ( infile,CurrentLine );
		ProcessLine ( CurrentLine );
	}

	infile.close();

	/* FIXME: This is hacked together and needs to be put into the
	 * above functions. However, I am lazy and sleepy, and this is
	 * not my code, so I'll leave that to the origional author ;)
	 */
	vector<TTriangle> triangleList;
	GLuint Texture = GTextureManager->getTexture(TexturePath);
	model->Texture = Texture;

	for(unsigned int i = 0; i < Faces.size(); ++i)
	{
		for (int j = 2; j < Faces[i].VectorLength() ; ++j)
		{
			TTriangle aTriangle;
			aTriangle.v1 = Vectors[Faces[i].GetGVector(0)];
			aTriangle.v2 = Vectors[Faces[i].GetGVector(j-1)];
			aTriangle.v3 = Vectors[Faces[i].GetGVector(j)];
			if (Faces[i].hasNormals)
			{
				aTriangle.n1 = NVectors[Faces[i].GetNVector(0)];
				aTriangle.n2 = NVectors[Faces[i].GetNVector(j-1)];
				aTriangle.n3 = NVectors[Faces[i].GetNVector(j)];
			}
			else
			{
				aTriangle.n1 = Faces[i].GetNormal();
				aTriangle.n2 = Faces[i].GetNormal();
				aTriangle.n3 = Faces[i].GetNormal();
			}
			if (Faces[i].hasTVecs)
			{
				aTriangle.uv1 = TexVectors[Faces[i].GetTVector(0)];
				aTriangle.uv2 = TexVectors[Faces[i].GetTVector(j-1)];
				aTriangle.uv3 = TexVectors[Faces[i].GetTVector(j)];
			}
			else
			{
				//assuming this model has no texture or a texture without a pattern
				aTriangle.uv1 = TVector(0,0,0);
				aTriangle.uv2 = TVector(1,1,0);
				aTriangle.uv3 = TVector(0,0,0);
			}
			triangleList.push_back(aTriangle);
		}
	}
	model->setTriangles(triangleList);
	return model;
}
